package com.saturday.web.filter.xss;

import org.apache.commons.lang3.StringUtils;
import org.springframework.web.util.HtmlUtils;

import java.util.regex.Pattern;

/**
 * xss清洗辅助类
 */
public class XssCleanUtils {
    /**
     * scrpit去空正则表达式
     */
    public static final String SCRIPT_SPACE = "script[ ]*";

    /**
     * scrpit字符
     */
    public static final String SCRIPT = "script";

    /**
     * script标签
     */
    public static final Pattern SCRIPT_TAG_PATTERN = Pattern.compile("<script>(.*?)</script>", Pattern.CASE_INSENSITIVE);

    /**
     * src '标签
     */
    public static final Pattern SRC_QUOTE_PATTERN = Pattern.compile("src[\r\n]*=[\r\n]*\\\'(.*?)\\\'", Pattern.CASE_INSENSITIVE | Pattern.MULTILINE | Pattern.DOTALL);

    /**
     * src "标签
     */
    public static final Pattern SRC_QUOTES_PATTERN = Pattern.compile("src[\r\n]*=[\r\n]*\\\"(.*?)\\\"", Pattern.CASE_INSENSITIVE | Pattern.MULTILINE | Pattern.DOTALL);

    /**
     * script结束标签
     */
    public static final Pattern SCRIPT_ONE_PATTERN = Pattern.compile("</script>", Pattern.CASE_INSENSITIVE);

    /**
     * script开始标签
     */
    public static final Pattern SCRIPT_ELLIPSIS_PATTERN = Pattern.compile("<script(.*?)>", Pattern.CASE_INSENSITIVE | Pattern.MULTILINE | Pattern.DOTALL);

    /**
     * eval表达式
     */
    public static final Pattern EVAL_PATTERN = Pattern.compile("eval\\((.*?)\\)", Pattern.CASE_INSENSITIVE | Pattern.MULTILINE | Pattern.DOTALL);

    /**
     * expression表达式
     */
    public static final Pattern EXPRESSION_PATTERN = Pattern.compile("expression\\((.*?)\\)", Pattern.CASE_INSENSITIVE | Pattern.MULTILINE | Pattern.DOTALL);

    /**
     * javascript表达式
     */
    public static final Pattern JAVASCRIPT_PATTERN = Pattern.compile("javascript:", Pattern.CASE_INSENSITIVE);

    /**
     * vbscript表达式
     */
    public static final Pattern VBSCRIPT_PATTERN = Pattern.compile("vbscript:", Pattern.CASE_INSENSITIVE);

    /**
     * onload事件
     */
    public static final Pattern ONLOAD_PATTERN = Pattern.compile("onload(.*?)=", Pattern.CASE_INSENSITIVE | Pattern.MULTILINE | Pattern.DOTALL);

    /**
     * http超链接表达式
     */
    public static final Pattern HTTP_PATTEN = Pattern.compile("https{0,1}:", Pattern.CASE_INSENSITIVE);

    /**
     * @param value 原始值
     * @return 清洗值
     * @方法名称 cleanScript
     * @功能描述 <pre>清洗script--xss弱校验时可用，比如富文本内容</pre>
     */
    public static String cleanScript(String value) {
        if (value != null) { // 推荐使用ESAPI库来避免脚本攻击,value = ESAPI.encoder().canonicalize(value);
            value = value.replaceAll(SCRIPT_SPACE, SCRIPT);
            // 避免script
            value = SCRIPT_TAG_PATTERN.matcher(value).replaceAll("");
            // 避免src形式的表达式
            value = SRC_QUOTE_PATTERN.matcher(value).replaceAll("");
            value = SRC_QUOTES_PATTERN.matcher(value).replaceAll("");
            // 删除单个的 </script> 标签
            value = SCRIPT_ONE_PATTERN.matcher(value).replaceAll("");
            // 删除单个的<script ...> 标签
            value = SCRIPT_ELLIPSIS_PATTERN.matcher(value).replaceAll("");
            // 避免 eval(...) 形式表达式
            value = EVAL_PATTERN.matcher(value).replaceAll("");
            // 避免 expression(...) 表达式
            value = EXPRESSION_PATTERN.matcher(value).replaceAll("");
            // 避免 javascript: 表达式
            value = JAVASCRIPT_PATTERN.matcher(value).replaceAll("");
            // 避免 vbscript:表达式
            value = VBSCRIPT_PATTERN.matcher(value).replaceAll("");
            // 避免 onload= 表达式
            value = ONLOAD_PATTERN.matcher(value).replaceAll("");
            // 避免 http 表达式
            value = HTTP_PATTEN.matcher(value).replaceAll("");
        }
        return value;
    }

    /**
     * xss字符清洗(先还原再转义，预防多次操作后无限转义)
     */
    public static String xssClean(String text) {
        // EcmaScript转义，这个暂时不开启
        // text = StringEscapeUtils.escapeEcmaScript(StringEscapeUtils.unescapeEcmaScript(text));
        // 或者使用StringEscapeUtils.escapeHtml4()
        if (StringUtils.isNotEmpty(text)) {
            text = HtmlUtils.htmlEscape(HtmlUtils.htmlUnescape(text));
        }
        return text;
    }
}
